release/v1.4.1#255
Conversation
## Why CODEOWNERS の `@araaki12345` を現室長である `@KikyoNanakusa`(https://github.com/KikyoNanakusa)に差し替える。 ## What - `CODEOWNERS` の1行を更新: `@araaki12345` → `@KikyoNanakusa` <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/typing/pull/253" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Why 依存アップグレード(Next.js 16 / React 19 / ESLint 9 フラットコンフィグ等)で `yarn build` / `yarn lint` / CI インストールが壊れていたので、ビルドチェーンを再び通るようにする。併せて調査中に判明した Tailwind / shadcn スタック一式や、役目を終えた依存の残骸も掃除する。 ## What ### Next.js 16 / ESLint 9 アップグレード対応 - `next lint` 廃止に伴う ESLint フラットコンフィグ移行(`eslint.config.mjs`) - Next.js 16 の purity lint / set-state-in-effect ルールに合わせた `GameTyping.tsx` / `RankingTabs.tsx` / `game/page.tsx` の修正 - `tsconfig.json` を Next.js の自動補正(`jsx: react-jsx` / `.next/dev/types/**` の include 追加)に追従 ### Tailwind / shadcn スタック撤去 実態として `@tailwind` ディレクティブも Tailwind ユーティリティクラスも使われていないため、関連パッケージ・設定ファイルを一式削除: - **dependencies** 削除: `clsx`, `tailwind-merge`, `tailwindcss-animate`, `@radix-ui/react-slot`, `@radix-ui/react-toast`, `class-variance-authority`, `lucide-react` - **devDependencies** 削除: `tailwindcss`, `@tailwindcss/postcss`, `postcss`, `autoprefixer` - **ファイル削除**: `tailwind.config.ts`, `postcss.config.js`, `src/libs/shadcn/` - autoprefixing は Next.js 組み込みの PostCSS パイプライン(`postcss-preset-env`)に委譲 ### 依存整備 - `@testing-library/react` の peer 要件である `@testing-library/dom` を明示追加 - `prettier` を `dependencies` → `devDependencies` に分類修正 - `@types/node` / `eslint` / `prettier` / `tsx` の patch / minor 追従 - `@next/codemod` が自動挿入した `@types/react` / `@types/react-dom` の `resolutions` ブロックを削除(React 19 エコシステム成熟により不要、むしろ古い `19.0.1` / `19.0.2` に固定される副作用が出ていた) - `openapi-typescript` v6 → v7 bump に合わせて `v1.d.ts` を v7 format で再生成 - `RankingTabs.tsx` の未使用 `useCallback` import 削除 - Yarn 4(Corepack)で `yarn.lock` をクリーン再生成 ## How - `tsconfig.json` の差分(`jsx` 変更・include 追加)は Next.js 16 が `next build` 時に自動で書き戻すもので、手動の設計変更ではない。ログ上も `jsx was set to react-jsx (next.js uses the React automatic runtime)` として明示される。 - `postcss.config.js` を削除することで Next.js 組み込みの PostCSS デフォルトにフォールバックする構成。最終出力 CSS(ベンダープレフィックス等)は従前と同じ。 - ESLint は `^9.39.4` 固定(v10 非対応): `eslint-plugin-react` が ESLint v10 で削除されたレガシー API(`context.getFilename()`)に依存しているため、上流修正 ([jsx-eslint/eslint-plugin-react#3979](jsx-eslint/eslint-plugin-react#3979)) が公開されるまで v9 に留める。参考: [vercel/next.js#89764](vercel/next.js#89764) - lint は 0 errors(既存の `<img>` 6 件と 1 件の `react-hooks/exhaustive-deps` 警告のみ残る。いずれも今回のスコープ外) ## Validation - `corepack yarn install --immutable` - `corepack yarn format:ci` - `corepack yarn lint` - `corepack yarn build` - `corepack yarn test --runInBand` ## References - [Next.js 16 upgrade guide](https://nextjs.org/docs/app/guides/upgrading/version-16) - [Next.js ESLint migration / flat config](https://nextjs.org/docs/app/api-reference/config/eslint) - [Next.js codemods](https://nextjs.org/docs/app/guides/upgrading/codemods) - [Tailwind CSS v4 upgrade guide](https://tailwindcss.com/docs/upgrade-guide) - [vercel/next.js#89764 — ESLint v10 incompatibility](vercel/next.js#89764) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
backmerge 1.4.0
|
|
||
| // 開始時刻と処理フラグの参照 | ||
| const startTimeRef = useRef<number>(Date.now()); | ||
| const startTimeRef = useRef<number>(0); |
There was a problem hiding this comment.
📝 Info: startTimeRef initialization change relies on useEffect ordering guarantees
The change from useRef<number>(Date.now()) to useRef<number>(0) at typing-app/src/components/templates/GameTyping.tsx:34 moves the timer start initialization to a useEffect at line 193-196. This works correctly because: (1) the timer interval callback (line 110-121) won't fire until 100ms after mount, well after all effects complete; (2) keypress handlers only fire on user input, also after mount; (3) the completion check effect at line 127-132 checks stats.typeIndex === subjectText.length - 1, which is false on mount for any text longer than 1 character (all sample texts are 1000+ chars). The one theoretical edge case is if subjectText.length === 1, the completion effect would fire before the init effect (due to declaration order), reading startTimeRef.current = 0 and computing a wildly incorrect elapsed time (~55 years). This is unrealistic given the actual text files but worth noting as a fragile ordering dependency.
Was this helpful? React with 👍 or 👎 to provide feedback.
| useEffect(() => { | ||
| let isCancelled = false; | ||
|
|
||
| const fetchData = async () => { | ||
| const { data, error } = await client.GET("/scores/ranking", { | ||
| params: { | ||
| query: { | ||
| sort_by: sortBy, | ||
| start: rankingStartFrom, | ||
| limit: LIMIT, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| if (data) { | ||
| setScoreRankings(data.rankings); | ||
| setTotalRankingCount(data.total_count); | ||
| } else { | ||
| showErrorToast(error); | ||
| } | ||
| }, [sortBy, rankingStartFrom]); | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| fetchData(); | ||
| }, [fetchData]); | ||
| if (isCancelled) { | ||
| return; | ||
| } | ||
|
|
||
| if (data) { | ||
| setScoreRankings(data.rankings); | ||
| setTotalRankingCount(data.total_count); | ||
| } else { | ||
| showErrorToast(error); | ||
| } | ||
| }; | ||
|
|
||
| void fetchData(); | ||
|
|
||
| return () => { | ||
| isCancelled = true; | ||
| }; | ||
| }, [refreshKey, sortBy, rankingStartFrom]); |
There was a problem hiding this comment.
📝 Info: RankingTabs refactor fixes a pre-existing stale-data bug on refresh
The old code called fetchData() directly in the refresh button handler after setRankingStartFrom(1). Since fetchData was a useCallback capturing rankingStartFrom in its closure, the direct call would use the stale (pre-update) value of rankingStartFrom, not the newly-set value of 1. The new approach using refreshKey state to trigger a useEffect ensures the fetch always uses the committed state values. This is a correctness improvement, not just a refactor.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const filenames = fs.readdirSync("public/texts/"); | ||
|
|
||
| const getRandomSubjectText = () => { | ||
| const randomFilename = filenames[Math.floor(Math.random() * filenames.length)] ?? filenames[0]; |
There was a problem hiding this comment.
📝 Info: game/page.tsx: nullish coalescing fallback is ineffective when filenames is empty
At line 7, filenames[Math.floor(Math.random() * filenames.length)] ?? filenames[0] — the ?? filenames[0] fallback exists to satisfy noUncheckedIndexedAccess: true in tsconfig. However, if filenames is empty, Math.floor(Math.random() * 0) yields 0, filenames[0] is undefined, and the fallback filenames[0] is also undefined, so fs.readFileSync('public/texts/undefined') would throw. This is a pre-existing issue (the old code would also fail on an empty directory) and is unrealistic in practice since public/texts/ contains sample files.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "jest": "^30.3.0", | ||
| "jest-environment-jsdom": "^30.3.0", | ||
| "openapi-typescript": "7.13.0", | ||
| "prettier": "^3.8.2", |
There was a problem hiding this comment.
📝 Info: prettier moved from dependencies to devDependencies
In the old package.json, prettier was listed under dependencies. It's now correctly placed in devDependencies at line 38. This is a proper fix — prettier is a development tool and should not be bundled as a production dependency.
Was this helpful? React with 👍 or 👎 to provide feedback.
Uh oh!
There was an error while loading. Please reload this page.